#include "precomp.h"
#include "WorldMap.h"

namespace Tmpl {

// Setup
WorldMap::WorldMap(int mapSize, int renderSize)
{
	mRenderSize = renderSize;
	mTextureCount = 0;
	mTextures = 0;

	// Allocate the map
	mMapSize = mapSize;
	mMap = MALLOC64(mMapSize * mMapSize, MapPos);

	// Reset Map
	for (int i=0; i<mMapSize*mMapSize; ++i)
	{
		mMap[i].height = 0.0f;
		for (int j=0; j<MAX_TEXTURES_PER_POS; ++j)
		{
			mMap[i].textures[j].texId = -1;
			mMap[i].textures[j].value = 0.0f;
		}	
	}

	// Load the shaders
	mShaderFlat = ShaderManager::LoadShader("Data/RenderFlat");
	mShader = ShaderManager::LoadShader("Data/Terrain");

	// Create the VBO
	glGenBuffers(1, &mVBO);
	mVboStrideSize = (MAX_RENDER_TEXTURES+8)*sizeof(float);
	glBufferData(GL_ARRAY_BUFFER, mRenderSize*mRenderSize*mVboStrideSize, 0, GL_STREAM_DRAW);

	// Generate Indices VBO
	int idx = 0;
	glGenBuffers(1, &mVBOIndices);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIndices);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mRenderSize-1)*(mRenderSize-1)*4*sizeof(GLuint), 0, GL_STATIC_DRAW);
	GLuint* ibuffer = (GLuint*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
	for (int y=0; y<mRenderSize-1; ++y)
	for (int x=0; x<mRenderSize-1; ++x)
	{
		ibuffer[idx++] = (y  ) * mRenderSize + (x  );
		ibuffer[idx++] = (y  ) * mRenderSize + (x+1);
		ibuffer[idx++] = (y+1) * mRenderSize + (x+1);
		ibuffer[idx++] = (y+1) * mRenderSize + (x  );
	}
	glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); 
}

WorldMap::~WorldMap()
{
	if (mTextures) FREE64(mTextures);
	FREE64(mMap);
	glDeleteBuffers(1, &mVBO);
	glDeleteBuffers(1, &mVBOIndices);
}

char WorldMap::AddTexture(const char* filename, float density, float slopeMin, float slopeMax, float heightMin, float heightMax)
{
	// Generate Texture Parameters
	if (mTextureCount == 0)
		mTextures = MALLOC64(mTextureCount + 1, MapTexture);	
	else
		mTextures = REALLOC64(mTextures, mTextureCount + 1, MapTexture);
	MapTexture& tex = mTextures[mTextureCount];
	strcpy_s(tex.name, filename);
	tex.density = density;
	tex.slopeMin = slopeMin;
	tex.slopeMax = slopeMax;
	tex.heightMin = heightMin;
	tex.heightMax = heightMax;

	// Create file handle
	FILE* fp;
	fopen_s(&fp, filename, "rb");
	if(!fp)
	{
		LOG_ERROR("Unable to open file: %s", filename);
		return -1;
	}

	// Load the pixels from file
	int w, h, format;
	Pixel* bits = (Pixel*)stbi_load_from_file(fp, &w, &h, &format, STBI_rgb_alpha);
	fclose(fp);
	if(!bits)
	{
		const char* reason = stbi_failure_reason();
		LOG_ERROR("Unable to load Image \"%s\"", filename);
		return false;
	}

	// Copy image bits
	Pixel* buffer = MALLOC64(w * h, Pixel);
	for(int y=0; y<h; ++y)
	{
		const Pixel* line = bits + y * w;
		Pixel* dst = buffer + y * w;
		for(int x=0; x<w; ++x) dst[x] = line[x];
	}
	stbi_image_free(bits);

	// Create OpenGL Image
	glEnable(GL_TEXTURE_2D);
	glGenTextures(1, &tex.textureId);
	glBindTexture(GL_TEXTURE_2D, tex.textureId);
	GLint wrapST = GL_REPEAT;
	GLint magFilter = GL_LINEAR;
	GLuint minFilter = GL_LINEAR_MIPMAP_LINEAR;
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	glGenerateMipmap(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_2D);
	FREE64(buffer);

	// it is valid, increment texture count
	return mTextureCount++;
}
// Logic
static int ClampAround(int v, int& size)
{
	while (v < 0) v += size;
	while (v >= size) v -= size;
	return v;
}
MapPos& WorldMap::GetMap(int x, int y)
{
	int mapX = ClampAround(x, mMapSize);
	int mapY = ClampAround(y, mMapSize);
	return mMap[mapY * mMapSize + mapX];
}
void WorldMap::Update(Vec3f playerPos)
{
	mRenderTextureCount = 0;

	// Resolve Player Cell
	int playerX = (int)(playerPos.x);
	int playerY = (int)(playerPos.z);

	// Bind VBO
	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	glBufferData(GL_ARRAY_BUFFER, mRenderSize*mRenderSize*mVboStrideSize, 0, GL_STREAM_DRAW);
	float* fbuffer = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

	// Update vertices
	int idx = 0;
	for (int y=0; y<mRenderSize; ++y)
	for (int x=0; x<mRenderSize; ++x)
	{
		// Get Map Data
		MapPos& m = GetMap(playerX + x, playerY + y);

		// Update Pos
		float fX = -mRenderSize*0.5f + float(x);
		float fZ = -mRenderSize*0.5f + float(y);
		fbuffer[idx++] = fX - fmodf(playerPos.x, 1.0f);
		fbuffer[idx++] = m.height - playerPos.y;
		fbuffer[idx++] = fZ - fmodf(playerPos.z, 1.0f);

		// Update Normal
		fbuffer[idx++] = 0.0f;
		fbuffer[idx++] = 0.0f;
		fbuffer[idx++] = 0.0f;

		// Update UV
		fbuffer[idx++] = float(playerX + x);
		fbuffer[idx++] = float(playerY + y);

		// Update Textures
		for (int i=0; i<MAX_RENDER_TEXTURES; ++i) fbuffer[idx + i] = 0.0f;
		for (int i=0; i<MAX_TEXTURES_PER_POS; ++i)
		{
			MapPosTexture& t = m.textures[i];
			if (t.texId >= 0)
			{
				// is this texture already in the render textures array?
				char arrayPos = -1;
				for (int j=0; j<mRenderTextureCount; ++j)
				{
					if (mRenderTextures[j] == t.texId)
					{
						arrayPos = j;
						break;
					}
				}
				if (arrayPos == -1)
				{
					arrayPos = mRenderTextureCount;
					mRenderTextures[mRenderTextureCount++] = t.texId;
				}

				// Now update this vertex
				fbuffer[idx + arrayPos] = t.value;
			}
		}
		idx += MAX_RENDER_TEXTURES;
	}

	// Calculate Normals
	idx = 0;
	for (int y=0; y<mRenderSize; ++y)
	for (int x=0; x<mRenderSize; ++x)
	{
		// pos
		idx += 3;

		// normal ( no need for normalize we will do that in shader)
		

		// uv + tex
		idx += 2 + MAX_RENDER_TEXTURES;
	}

	glUnmapBuffer(GL_ARRAY_BUFFER); 
}

float WorldMap::GetHeightAtPos(Vec2f pos)
{
	// Resolve Player Cell
	float x = pos.x;
	float y = pos.y;

	int x0 = (int)x;
	int x1 = x0 + 1;
	int y0 = (int)y;
	int y1 = y0 + 1;

	//Look up the height values
	float h00 = GetMap(x0,y0).height;
	float h01 = GetMap(x0,y1).height;
	float h10 = GetMap(x1,y0).height;
	float h11 = GetMap(x1,y1).height;

	float tx = x - float(x0);
	float ty = y - float(y0);

	//Use a bilerp to find the final height value
	float txty = tx * ty;
	return h00 * ( 1.0f - ty - tx + txty ) + h10 * ( tx - txty ) + h11 * txty + h01 * ( ty - txty );
}
// Rendering
void WorldMap::BindVBO(ShaderProgram* shader)
{
	GLint vertexLoc = shader->GetAttribLoc("InVertex");
	GLint normalLoc = shader->GetAttribLoc("InNormal");
	GLint texCoord0Loc = shader->GetAttribLoc("InTexCoord0");

	if (vertexLoc >= 0) glEnableVertexAttribArray(vertexLoc);
	if (normalLoc >= 0) glEnableVertexAttribArray(normalLoc);
	if (texCoord0Loc >= 0) glEnableVertexAttribArray(texCoord0Loc);

	glBindBuffer(GL_ARRAY_BUFFER, mVBO);
	if (vertexLoc >= 0) glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, mVboStrideSize, 0);
	if (normalLoc >= 0) glVertexAttribPointer(normalLoc, 3, GL_FLOAT, GL_FALSE, mVboStrideSize, (GLvoid*)((char*)NULL+sizeof(float)*3));
	if (texCoord0Loc >= 0) glVertexAttribPointer(texCoord0Loc, 2, GL_FLOAT, GL_FALSE, mVboStrideSize, (GLvoid*)((char*)NULL+sizeof(float)*6));
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIndices);
}

void WorldMap::UnbindVBO(ShaderProgram* shader)
{
	GLint vertexLoc = shader->GetAttribLoc("InVertex");
	GLint normalLoc = shader->GetAttribLoc("InNormal");
	GLint texCoord0Loc = shader->GetAttribLoc("InTexCoord0");
	if (vertexLoc >= 0) glDisableVertexAttribArray(vertexLoc);
	if (normalLoc >= 0) glDisableVertexAttribArray(normalLoc);
	if (texCoord0Loc >= 0) glDisableVertexAttribArray(texCoord0Loc);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void WorldMap::RenderFlat(Matrix4x4f projection, Matrix4x4f modelView)
{
	mShaderFlat->Use();
	mShaderFlat->UniformMatrix4x4("matProjModelView", projection * modelView);

	BindVBO(mShaderFlat);

	glDrawElements(GL_QUADS, (mRenderSize-1)*(mRenderSize-1)*4, GL_UNSIGNED_INT, 0);

	UnbindVBO(mShaderFlat);
}

void WorldMap::RenderTextured(Matrix4x4f projection, Matrix4x4f modelView)
{
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_TEXTURE_2D);

	mShader->Use();
	mShader->Uniform1i("TerrainTex1", 0);
	mShader->Uniform1i("TerrainTex2", 1);
	mShader->UniformMatrix4x4("matProjModelView", projection * modelView);
	mShader->UniformMatrix4x4("matModelView", modelView);

	BindVBO(mShader);
	GLint texValueLoc = mShader->GetAttribLoc("InTexValue");
	glEnableVertexAttribArray(texValueLoc);

	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE);
	GLuint texId = 0;
	for (int i=0; i<mRenderTextureCount; i+=2)
	{
		glActiveTexture(GL_TEXTURE1);
		if (mRenderTextureCount > i+1)
		{
			texId = mTextures[mRenderTextures[i+1]].textureId;
			glBindTexture(GL_TEXTURE_2D, texId);
		}
		else
		{
			glBindTexture(GL_TEXTURE_2D, 0);
		}

		glActiveTexture(GL_TEXTURE0);
		texId = mTextures[mRenderTextures[i]].textureId;
		glBindTexture(GL_TEXTURE_2D, texId);

		glVertexAttribPointer(texValueLoc, 2, GL_FLOAT, GL_FALSE, mVboStrideSize, (GLvoid*)((char*)NULL+sizeof(float)*(8+i)));
		glDrawElements(GL_QUADS, (mRenderSize-1)*(mRenderSize-1)*4, GL_UNSIGNED_INT, 0);
	}
	glDisable(GL_BLEND);

	glDisableVertexAttribArray(texValueLoc);
	UnbindVBO(mShader);
}
void WorldMap::Render(Matrix4x4f projection, Matrix4x4f modelView)
{     
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);

	//glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

	// First pass, leave behind a depth entry
	glColorMask(false, false, false, false);
	RenderFlat(projection, modelView);

	// Second Pass, render the real geometry
	glColorMask(true, true, true, true);
	glDepthFunc(GL_EQUAL);
	RenderTextured(projection, modelView);
	glDepthFunc(GL_LEQUAL);

	//glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

	// Were done
	mShader->UnUse();

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);

	glDisable(GL_CULL_FACE);
}
// Generation
static inline float SCurve(float a)
{
    return (a * a * (3.0f - 2.0f * a));
}
static inline int Noise2D(int x, int y)
{     
	int n = (1619 * x + 31337 * y) & 0x7fffffff;
    n = (n >> 13) ^ n;
	n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff;
	return n;
}
static inline float NoiseToFloat(int n)
{
	return 1.0f - ((float)(n) / 1073741824.0f);
}
static float CoherentNoise2D(float x, float y)  
{  
	int ix = (int)x;
	int iy = (int)y;
	
	float s = NoiseToFloat(Noise2D(ix  , iy	 ));   
	float t = NoiseToFloat(Noise2D(ix+1, iy	 ));   
	float u = NoiseToFloat(Noise2D(ix  , iy+1)); 
	float v = NoiseToFloat(Noise2D(ix+1, iy+1)); 
	
	float sx = SCurve(x - float(ix));
	float iu = Lerp(s, t, sx);
	float iv = Lerp(u, v, sx);
	
	float sy = SCurve(y - float(iy));
	return Lerp(iu, iv, sy);
} 

static float TurbulenceNoise(float x, float y, float persistance, int octaves) 
{
	float total = 0.0;
	float frequency = 1.0f;
	float amplitude = persistance;
	for (int i=0; i<octaves; ++i)
	{
		total += fabs(CoherentNoise2D(x * frequency, y * frequency) * amplitude);
		amplitude *= persistance;
		frequency *= 2.0f;
	}
	return total;
}
static float FractalNoise(float x, float y, float persistance, int octaves) 
{
	float total = 0.0;
	float frequency = 1.0f;
	float amplitude = persistance;
	for (int i=0; i<octaves; ++i)
	{
		total += CoherentNoise2D(x * frequency, y * frequency) * amplitude;
		amplitude *= persistance;
		frequency *= 2.0f;
	}
	return total;
}
static float GetHeightAtPos(float x, float y)
{
	float turbulence = -Max(0.0f, TurbulenceNoise(x*0.05f, y*0.05f, 0.5f, 3)*5.0f - 3.0f);
	float noise = -Max(0.0f, CoherentNoise2D(x*0.03f, y*0.03f) * 3.0f - 1.5f);
	float bump = CoherentNoise2D(x*0.1f, y*0.1f) * 1.5f;
	return turbulence + noise + bump;
}
static float GetTextureAtPos(float x, float y, int texCount, float density)
{
	float xx = x + float(texCount*23244);
	float yy = y + float(texCount*997);
	float turbulence = Max(0.00f, TurbulenceNoise(xx*0.15f, yy*0.15f, 0.5f, 3)*2.0f - 0.75f);
	float noise = Max(0.00f, FractalNoise(xx*0.1f, yy*0.1f, 0.5f, 3)*2.0f) * density;
	float bump = Max(0.00f, CoherentNoise2D(xx*0.2f, yy*0.2f)*0.3f) * density;
	return Clamp(density*0.1f, 1.0f, turbulence + noise + bump);
}
static float TileableTextureAtPos(int X, int Y, float S, int texCount, float density)
{
	float x = fmod(float(X), S);
	float y = fmod(float(Y), S);
	float n1 = (S - x) * (S - y) * GetTextureAtPos(x+S,y+S,texCount,density);
	float n2 = (x    ) * (S - y) * GetTextureAtPos(x  ,y+S,texCount,density);
	float n3 = (x    ) * (y    ) * GetTextureAtPos(x  ,y  ,texCount,density);
	float n4 = (S - x) * (y    ) * GetTextureAtPos(x+S,y  ,texCount,density);
	return (n1 + n2 + n3 + n4) / (S * S);
}
static float TileableHeightAtPos(int X, int Y, float S)
{
	float x = fmod(float(X), S);
	float y = fmod(float(Y), S);
	float n1 = (S - x) * (S - y) * GetHeightAtPos(x+S,y+S);
	float n2 = (x    ) * (S - y) * GetHeightAtPos(x  ,y+S);
	float n3 = (x    ) * (y    ) * GetHeightAtPos(x  ,y  );
	float n4 = (S - x) * (y    ) * GetHeightAtPos(x+S,y  );
	return (n1 + n2 + n3 + n4) / (S * S);
}
void WorldMap::GenerateMap(int seed)
{
	mSeed = seed;

	// Generate a heightmap
	float fMapSize = float(mMapSize);
	for (int y=0; y<mMapSize; ++y)
	for (int x=0; x<mMapSize; ++x)
	{
		mMap[y * mMapSize + x].height = TileableHeightAtPos(x, y, fMapSize);
	}

	// Generate Textures
	for (int y=0; y<mMapSize; ++y)
	for (int x=0; x<mMapSize; ++x)
	{
		int idx = y * mMapSize + x;

		// nulify textures
		for (int j=0; j<MAX_TEXTURES_PER_POS; ++j)
		{
			mMap[idx].textures[j].texId = -1;
			mMap[idx].textures[j].value = 0.0f;
		}

		// calculate the variables
		int texCount = 0;
		float totalValue = 0.0f;

		// get neighbors
		float heights[9];
		float average = 0.0f;
		float lowest = 100000.0f;
		float highest = -10000.0f;
		for (int yy=-1; yy<=1; ++yy)
		for (int xx=-1; xx<=1; ++xx)
		{
			float h = GetMap(x+xx,y+yy).height;
			heights[(yy+1)*3+(xx+1)] = h;
			average += h;
			if (h < lowest) lowest = h;
			if (h > highest) highest = h;
		}
		average /= 9;

		// calculate height and slope modifier
		float height = Clamp(0.001f, 0.999f, (GetMap(x,y).height+average) * -0.1f);
		float slope = Clamp(0.001f, 0.999f, 0.5f * fabs(mMap[idx].height - average));
		for (int i=0; i<mTextureCount; ++i)
		{
			MapTexture& t = mTextures[i];

			// add the textures
			if (t.density > 0.0f && texCount < MAX_TEXTURES_PER_POS)
			{
				if (t.heightMax >= height && t.heightMin <= height &&
					t.slopeMax >= slope && t.slopeMin <= slope)
				{
					float heightModifier = 4.0f * Max(0.25f,Min(t.heightMax - height, height - t.heightMin));
					float value = TileableTextureAtPos(x, y, fMapSize, texCount, t.density * heightModifier * 2.0f); 
					if ( value > 0.0f)
					{
						mMap[idx].textures[texCount].texId = i;
						mMap[idx].textures[texCount++].value = value;
						totalValue += value;
						if (totalValue >= 0.9999f) break;
					}
				}
			}
		}

		// now normalize the texture
		totalValue = 1.0f / totalValue;
		for (int j=0; j<MAX_TEXTURES_PER_POS; ++j)
		{
			mMap[idx].textures[j].value *= totalValue;
		}				
	}
}
}